package net.anzix.jhacks.flexclipse.builder.hyperlinks;

import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.hyperlink.IHyperlink;
import org.eclipse.jface.text.hyperlink.IHyperlinkDetector;
import org.eclipse.wst.sse.core.StructuredModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IModelManager;
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel;
import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class MXMLHyperlinkDetector implements IHyperlinkDetector {

	public IHyperlink[] detectHyperlinks(final ITextViewer textViewer,
			final IRegion region, final boolean canShowMultipleHyperlinks) {
		
		IHyperlink link = getTagLink(textViewer, region);
		if(link == null) {
			link = getAttributeValueLink(textViewer, region);
		}
		if(link == null) {
			link = getAttributeLink(textViewer, region);
		}
		if(link == null) {
			link = getScriptLink(textViewer, region);
		}

		return singleLinkArrayOrNull(link);
	}

	private IHyperlink[] singleLinkArrayOrNull(final IHyperlink link) {
		return link == null ? null : new IHyperlink[]{link} ;
	}

	private IHyperlink getAttributeValueLink(ITextViewer textViewer,
			IRegion region) {
		final int offset = region.getOffset();
		final IDocument document = textViewer.getDocument();
		
		final Node node = getNodeByOffset(document, offset);
		if(node == null) {
			return null;
		}
		final Attr att = getAttributeByOffset(node, offset);
		if(att == null) {
			return null;
		}
		final String valueByOffset = getAttributeValueByOffset(att, offset);
		if(valueByOffset == null) {
			return null;
		}
		final int attStartOffset = ((IndexedRegion)att).getStartOffset();
		final int attOffset = offset -attStartOffset;
		
		final int start = valueByOffset.lastIndexOf('{', attOffset);
		final int end = valueByOffset.indexOf('}', attOffset);
		
		if(start == -1 || end == -1) {
			return null;
		}
		
		final String expr = valueByOffset.substring(start, end + 1).replace("{", "").replace("}", "");
		
		String[] path = expr.split("\\.");
		//TODO: This could be more correct here...
		final int offsetInExpr = offset - attStartOffset - start;
		
		Element element = att.getOwnerElement().getOwnerDocument().getDocumentElement();
		int pos = 0;
		for(String elemName : path) {
			final Element subElement = findElement(elemName, element);
			if(subElement == null) {
				break;
			}
			element = subElement;
			pos += elemName.length() + 1;
			if(pos >= offsetInExpr) {
				break;
			}
		}

		if(element == null) {
			return null;
		}

		//TODO: the magic here...
		return new MXMLTagHyperlink(new IWorkspaceRunnable(){

			public void run(IProgressMonitor monitor) throws CoreException {
				// TODO Auto-generated method stub
				
			}}, null, null);
	}

	private Element findElement(String elemName, Element element) {
		final NodeList childNodes = element.getChildNodes();
		for(int i = 0; i< childNodes.getLength(); i++) {
			final Node node = childNodes.item(i);
			if(node instanceof Element) {
				final String id = ((Element)node).getAttribute("id");
				if(elemName.equals(id)) {
					return ((Element)node);
				}
				Element child = findElement(elemName, ((Element)node));
				if(child != null) {
					return child;
				}
			}
		}
		return null;
	}

	IHyperlink getTagLink(final ITextViewer textViewer,
			final IRegion region) {
		final IDocument document = textViewer.getDocument();
		//final int offset = region.getOffset();
		final IModelManager modelManager = StructuredModelManager.getModelManager();
		final IStructuredModel model = modelManager.getExistingModelForRead(document);
		IndexedRegion indexedRegion = model.getIndexedRegion(region.getOffset());
		
		
		
		return null;
	}

	private IHyperlink getAttributeLink(final ITextViewer textViewer,
			final IRegion region) {
		
		final int offset = region.getOffset();
		final IDocument document = textViewer.getDocument();
		
		final Node node = getNodeByOffset(document, offset);
		if(node != null) {
			final Attr attr = getAttributeByOffset(node, offset);
			final String value = attr.getValue();
			
		}
		
		return null;
	}

	IHyperlink getScriptLink(final ITextViewer textViewer,
			final IRegion region) {
		return null;
	}

	private Node getNodeByOffset(final IDocument document, final int offset) {
		IndexedRegion inode = null;
		IStructuredModel sModel = null;
		try {
			sModel = org.eclipse.wst.sse.core.StructuredModelManager
					.getModelManager().getExistingModelForRead(document);
			if (sModel == null && document instanceof IStructuredDocument) {
				sModel = org.eclipse.wst.sse.core.StructuredModelManager
						.getModelManager().getModelForRead(
								(IStructuredDocument) document);
			}
			inode = sModel.getIndexedRegion(offset);
			if (inode == null) {
				inode = sModel.getIndexedRegion(offset - 1);
			}
		}
		finally {
			if (sModel != null) {
				sModel.releaseFromRead();
			}
		}

		if (inode instanceof Node) {
			return (Node) inode;
		}
		return null;

	}
	
	private String getAttributeValueByOffset(final Attr attr, final int offset) {
		final int startOffset = ((IndexedRegion)attr).getStartOffset();
		final int endOffset = ((IndexedRegion)attr).getEndOffset();
		if(offset <= endOffset && startOffset <= offset) {
			return attr.getValue();
		}
		return null;
	}
	
	private Attr getAttributeByOffset(final Node node, final int offset) {
		if ((node instanceof IndexedRegion)
				&& ((IndexedRegion) node).contains(offset)
				&& (node.hasAttributes())) {
			NamedNodeMap attrs = node.getAttributes();
			// go through each attribute in node and if attribute contains
			// offset, return that attribute
			for (int i = 0; i < attrs.getLength(); ++i) {
				// assumption that if parent node is of type IndexedRegion,
				// then its attributes will also be of type IndexedRegion
				IndexedRegion attRegion = (IndexedRegion) attrs.item(i);
				if (attRegion.contains(offset)) {
					return (Attr) attrs.item(i);
				}
			}
		}
		return null;

	}
	
}
